import chalk from "chalk"
import { MsgHandlerManager, ParsedServerInput, TransportDataUtil } from "tsrpc-base-client"
import { BaseServiceType } from "tsrpc-proto"
import { PrefixLogger } from "../models/PrefixLogger"
import { ApiCall } from "./ApiCall"
import { BaseServer, MsgHandler } from "./BaseServer"
import { MsgCall } from "./MsgCall"

export interface BaseConnectionOptions<ServiceType extends BaseServiceType = any> {
  /** Created by server, each Call has a unique id. */
  id: string
  /** Client IP address */
  ip: string
  server: BaseServer<ServiceType>
  dataType: "text" | "buffer" | "json"
}

export abstract class BaseConnection<ServiceType extends BaseServiceType = any> {
  /** It is long connection or short connection */
  abstract readonly type: "LONG" | "SHORT"

  protected abstract readonly ApiCallClass: { new (options: any): ApiCall }
  protected abstract readonly MsgCallClass: { new (options: any): MsgCall }

  /** Connection unique ID */
  readonly id: string
  /** Client IP address */
  readonly ip: string
  readonly server: BaseServer<ServiceType>
  readonly logger: PrefixLogger
  dataType: BaseConnectionOptions["dataType"]

  constructor(options: BaseConnectionOptions<ServiceType>, logger: PrefixLogger) {
    this.id = options.id
    this.ip = options.ip
    this.server = options.server
    this.logger = logger
    this.dataType = options.dataType
  }

  abstract get status(): ConnectionStatus
  /** Close the connection */
  abstract close(reason?: string): void

  /** Send buffer (with pre-flow and post-flow) */
  async sendData(
    data: string | Uint8Array | object,
    call?: ApiCall
  ): Promise<{ isSucc: true } | { isSucc: false; errMsg: string; canceledByFlow?: string }> {
    // Pre Flow
    let pre = await this.server.flows.preSendDataFlow.exec(
      { conn: this, data: data, call: call },
      call?.logger || this.logger
    )
    if (!pre) {
      return {
        isSucc: false,
        errMsg: "Canceled by preSendDataFlow",
        canceledByFlow: "preSendDataFlow",
      }
    }
    data = pre.data

    // @deprecated Pre Buffer Flow
    if (data instanceof Uint8Array) {
      let preBuf = await this.server.flows.preSendBufferFlow.exec(
        { conn: this, buf: data, call: call },
        call?.logger || this.logger
      )
      if (!preBuf) {
        return {
          isSucc: false,
          errMsg: "Canceled by preSendBufferFlow",
          canceledByFlow: "preSendBufferFlow",
        }
      }
      data = preBuf.buf
    }

    // debugBuf log
    if (this.server.options.debugBuf) {
      if (typeof data === "string") {
        ;(call?.logger ?? this.logger)?.debug(`[SendText] length=${data.length}`, data)
      } else if (data instanceof Uint8Array) {
        ;(call?.logger ?? this.logger)?.debug(`[SendBuf] length=${data.length}`, data)
      } else {
        ;(call?.logger ?? this.logger)?.debug("[SendJSON]", data)
      }
    }

    return this.doSendData(data, call)
  }
  protected abstract doSendData(
    data: string | Uint8Array | object,
    call?: ApiCall
  ): Promise<{ isSucc: true } | { isSucc: false; errMsg: string }>

  makeCall(input: ParsedServerInput): ApiCall | MsgCall {
    if (input.type === "api") {
      return new this.ApiCallClass({
        conn: this,
        service: input.service,
        req: input.req,
        sn: input.sn,
      })
    } else {
      return new this.MsgCallClass({
        conn: this,
        service: input.service,
        msg: input.msg,
      })
    }
  }

  /**
   * Send message to the client, only be available when it is long connection.
   * @param msgName
   * @param msg - Message body
   * @returns Promise resolved when the buffer is sent to kernel, it not represents the server received it.
   */
  async sendMsg<T extends string & keyof ServiceType["msg"]>(
    msgName: T,
    msg: ServiceType["msg"][T]
  ): ReturnType<BaseConnection["sendData"]> {
    if (this.type === "SHORT") {
      this.logger.warn("[SendMsgErr]", `[${msgName}]`, "Short connection cannot sendMsg")
      return { isSucc: false, errMsg: "Short connection cannot sendMsg" }
    }

    let service = this.server.serviceMap.msgName2Service[msgName as string]
    if (!service) {
      this.logger.warn("[SendMsgErr]", `[${msgName}]`, `Invalid msg name: ${msgName}`)
      return { isSucc: false, errMsg: `Invalid msg name: ${msgName}` }
    }

    // Pre Flow
    let pre = await this.server.flows.preSendMsgFlow.exec(
      { conn: this, service: service, msg: msg },
      this.logger
    )
    if (!pre) {
      this.logger.debug("[preSendMsgFlow]", "Canceled")
      return {
        isSucc: false,
        errMsg: "Canceled by preSendMsgFlow",
        canceledByFlow: "preSendMsgFlow",
      }
    }
    msg = pre.msg

    // Encode
    let opServerOutput = TransportDataUtil.encodeServerMsg(
      this.server.tsbuffer,
      service,
      msg,
      this.dataType,
      this.type
    )
    if (!opServerOutput.isSucc) {
      this.logger.warn("[SendMsgErr]", `[${msgName}]`, opServerOutput.errMsg)
      return opServerOutput
    }

    // Do send!
    this.server.options.logMsg &&
      this.logger.log(chalk.cyan.underline(`[Msg:${msgName}]`), chalk.green("[SendMsg]"), msg)
    let opSend = await this.sendData(opServerOutput.output)
    if (!opSend.isSucc) {
      return opSend
    }

    // Post Flow
    await this.server.flows.postSendMsgFlow.exec(pre, this.logger)

    return { isSucc: true }
  }

  // 多个Handler将异步并行执行
  private _msgHandlers?: MsgHandlerManager
  /**
   * Add a message handler,
   * duplicate handlers to the same `msgName` would be ignored.
   * @param msgName
   * @param handler
   */
  listenMsg<
    Msg extends string & keyof ServiceType["msg"],
    Call extends MsgCall<ServiceType["msg"][Msg]>,
  >(msgName: Msg, handler: MsgHandler<Call>): MsgHandler<Call> {
    if (!this._msgHandlers) {
      this._msgHandlers = new MsgHandlerManager()
    }
    this._msgHandlers.addHandler(msgName as string, handler)
    return handler
  }
  /**
   * Remove a message handler
   */
  unlistenMsg<
    Msg extends string & keyof ServiceType["msg"],
    Call extends MsgCall<ServiceType["msg"][Msg]>,
  >(msgName: Msg, handler: Function): void {
    if (!this._msgHandlers) {
      this._msgHandlers = new MsgHandlerManager()
    }
    this._msgHandlers.removeHandler(msgName as string, handler)
  }
  /**
   * Remove all handlers from a message
   */
  unlistenMsgAll<
    Msg extends string & keyof ServiceType["msg"],
    Call extends MsgCall<ServiceType["msg"][Msg]>,
  >(msgName: Msg): void {
    if (!this._msgHandlers) {
      this._msgHandlers = new MsgHandlerManager()
    }
    this._msgHandlers.removeAllHandlers(msgName as string)
  }
}

export enum ConnectionStatus {
  Opened = "OPENED",
  Closing = "CLOSING",
  Closed = "CLOSED",
}
